package com.bloom.demo.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

/**
 * BloomFilter based on redis
 */
@Slf4j
@Component
public class BloomFilter implements ApplicationRunner {

    @Autowired
    private RedisUtil redisUtil;

    private String hosts;

    private int maxKey = (int) Math.pow(2, 8);

    private float errorRate = 0.00000001f;

    private int hashFunctionCount;

    private int bitSize;

    public String defaultKey = "redis:bloomfilter:lotteryCode:";

    public String sameUserKey = "redis:bloomfilter:sameUser:";

    private int timeout = 1000;

    private int database;
    private String masterName;
    private String sentinelHosts;
    private String password;


    @Override
    public void run(ApplicationArguments args) throws Exception {
        init();
    }

    public void init() {
        bitSize = calcOptimalM(maxKey, errorRate);
        hashFunctionCount = calcOptimalK(bitSize, maxKey);
    }


//    /**
//     * add one object, using default key
//     *
//     * @param bizId
//     */
//    public void add(String bizId,int time) {
//        add(defaultKey, bizId, time);
//    }

    /**
     * add one object using the specified key
     *
     * @param key
     * @param bizId
     */
    public void add(String key, String bizId, int time) {
        int[] offset = HashUtils.murmurHashOffset(bizId, hashFunctionCount, bitSize);
        try {
            for (int i : offset) {
                redisUtil.setBit(key, i);
            }
            if (time > 0) {
                redisUtil.expire(key, time);
            }
        } catch (Exception e) {
            log.error("redis add error!", e);
        }
    }


    /**
     * Check if a bizId is part of the set
     *
     * @param key
     * @param bizId
     */
    public boolean include(String key, String bizId) {
        int[] offset = HashUtils.murmurHashOffset(bizId, hashFunctionCount, bitSize);
        try {
            for (int i : offset) {
                if (!redisUtil.getBit(key, i)) {
                    return false;
                }
            }
        } catch (Exception e) {
            log.error("redis include error!", e);
        }
        return true;
    }


    /**
     * Calculate M and K
     * See http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives for more details
     *
     * @param maxKey
     * @param errorRate
     * @return
     */
    public int calcOptimalM(int maxKey, float errorRate) {
        return (int) Math.ceil(maxKey * (Math.log(errorRate) / Math.log(0.6185)));
    }

    /**
     * Calculate M and K
     * See http://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives for more details
     *
     * @param bitSize
     * @param maxKey
     * @return
     */
    public int calcOptimalK(int bitSize, int maxKey) {
        return (int) Math.ceil(Math.log(2) * (bitSize / maxKey));
    }

    public String getHosts() {
        return hosts;
    }

    public void setHosts(String hosts) {
        this.hosts = hosts;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }


    public int getHashFunctionCount() {
        return hashFunctionCount;
    }

    public void setHashFunctionCount(int hashFunctionCount) {
        this.hashFunctionCount = hashFunctionCount;
    }

    public int getBitSize() {
        return bitSize;
    }

    public void setBitSize(int bitSize) {
        this.bitSize = bitSize;
    }


    public String getDefaultKey() {
        return defaultKey;
    }

    public void setDefaultKey(String defaultKey) {
        this.defaultKey = defaultKey;
    }

    public String getSameUserKey() {
        return sameUserKey;
    }

    public void setSameUserKey(String sameUserKey) {
        this.sameUserKey = sameUserKey;
    }

    public int getDatabase() {
        return database;
    }

    public void setDatabase(int database) {
        this.database = database;
    }

    public String getMasterName() {
        return masterName;
    }

    public void setMasterName(String masterName) {
        this.masterName = masterName;
    }

    public String getSentinelHosts() {
        return sentinelHosts;
    }

    public void setSentinelHosts(String sentinelHosts) {
        this.sentinelHosts = sentinelHosts;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


}
